#ifndef FTSM_BASE_H
#define FTSM_BASE_H

#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <string>
#include <vector>
#include <map>

namespace ftsm
{
    enum FTSM_States : std::uint16_t {
        NoneStates    = 0x0000U,
        START         = 0x0001U,
        INITIALISING  = 0x0002U,
        CONFIGURING   = 0x0003U,
        READY         = 0x0004U,
        RUNNING       = 0x0005U,
        RECOVERING    = 0x0006U,
        STOPPED       = 0x0007U,
    };

    enum FTSM_TransStatus : std::uint16_t {
        None                = 0x0000U,

        INITIALISED         = 0x0001U,      // initialised
        INIT_FAILED         = 0x0002U,      // initialisation_failed

        DONE_CONFIGURING    = 0x0101U,      // config_successful
        RECONFIGURE         = 0x0102U,      // configure
        DONE_RECONFIGURING  = 0x0103U,      // reconfig_successful
        RETRY_CONFIG        = 0x0104U,      // retry_config
        FAILED_CONFIG       = 0x0105U,      // config_failure

        WAIT                = 0x0201U,      // wait
        RUN                 = 0x0202U,      // run
        CONTINUE            = 0x0203U,      // continue_running
        DONE                = 0x0204U,      // done

        RECOVER             = 0x0301U,      // recover
        DONE_RECOVERING     = 0x0302U,      // recovery_successful
        FAILED_RECOVERY     = 0x0303U,      // failed_recovery
    };

    typedef struct tagTransaction {
        int                 id;         // id of this task
        unsigned int        checkSum;   // a random number for checking
        FTSM_TransStatus    (*fp)(int transId, int *nextTransId);
                                        // function pointer to the function body,
                                        // return Id of the next transaction
    } Transaction;

    /**
     * Base class of a fault-tolerant state machine
     *
     * @author Alex Mitrevski
     * @maintainer Alex Mitrevski, Santosh Thoduka, Argentina Ortega Sainz
     * @contact aleksandar.mitrevski@h-brs.de, santosh.thoduka@h-brs.de, argentina.ortega@h-brs.de
     */
    class FTSM
    {
    public:
        FTSM(std::string name, std::vector<std::string> dependencies, int max_recovery_attempts=1);

        /**
         * Starts the state machine on a background thread
         */
        void run();

        /**
         * Stops the state machine
         */
        void stop();

        /**
         * Abstract method for component initialisation
         */
        virtual FTSM_TransStatus init() = 0;

        /**
         * Abstract method for component configuration/reconfiguration
         */
        virtual FTSM_TransStatus configuring() = 0;

        /**
         * Abstract method for the behaviour of a component when it is ready for operation, but not active
         */
        virtual FTSM_TransStatus ready() = 0;

        /**
         * Abstract method for the behaviour of a component during active operation
         */
        virtual FTSM_TransStatus running() = 0;

        /**
         * Abstract method for component recovery
         */
        virtual FTSM_TransStatus recovering() = 0;

        /**
         * Indicates whether the state machine is running
         */
        bool is_running;

        /**
         * Indicates whether the state machine thread is alive
         */
        bool is_alive;
    protected:
        /**
         * The name of the component
         */
        std::string name;

        /**
         * The current state of the component
         */
        FTSM_States current_state;

        /**
         * A list of component dependencies
         */
        std::vector<std::string> dependencies;

        /**
         * Indicates whether the component has been configured
         */
        bool configured;

        /**
         * The maximum number of times the recovery behaviour should be repeated before we consider the component to have failed
         */
        int max_recovery_attempts;

        Transaction   **trans;
        int             current_transaction;

    private:
        /**
         * Performs component initialisation; calls the virtual this->init method
         * and returns a transition constant
         */
        FTSM_TransStatus __init();

        /**
         * Performs component configuration; calls the virtual this->configuring
         * method and returns a transition constant
         */
        FTSM_TransStatus __configuring();

        /**
         * Indicates component readiness; calls the virtual this->ready method
         * and returns a transition constant when an operation request is received
         */
        FTSM_TransStatus __ready();

        /**
         * Performs component operation; calls the virtual this->running method
         * and returns a transition constant
         */
        FTSM_TransStatus __running();

        /**
         * Performs component recovery; calls the virtual this->recovering method
         * and returns a transition constant
         */
        FTSM_TransStatus __recovering();

        /**
         * Manages the operation of the state machine by calling the appropriate
         * state methods and performing state machine transitions based on the
         * results of the states
         */
        void __manage_sm();

        /**
         * Performs a state machine transition as indicated by the given transition constant
         *
         * @param transition an FTSMTransitions constant indicating the transition
         *        that should be taken
         */
        void __transition(FTSM_TransStatus transition);

        /**
         * The previous state of the state machine (used for back-transitioning
         * in case of reconfiguration and recovery)
         */
        FTSM_States previous_state;

        /**
         * Thread for running the state machine
         */
        std::thread sm_thread;

        /**
         * A map of the possible transitions from each state
         */
        static std::map<FTSM_States, std::map<FTSM_TransStatus, FTSM_States>> transition_map;
    };

} // namespace ftsm

#endif
